package com.fsd.core.wechat.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fsd.core.common.BusinessException;
import com.fsd.core.util.EhcacheUtil;
import com.fsd.core.wechat.entity.AccessToken;
import com.fsd.core.wechat.entity.menu.Button;
import com.fsd.core.wechat.entity.menu.Menu;

/**
 * 公众平台通用接口工具类
 */
public class WeixinUtil {
	private static Logger log = LoggerFactory.getLogger(WeixinUtil.class);

	/** 
	 * 获取access_token 字符串
	 * @param appid 凭证 
	 * @param appsecret 密钥 
	 * @return 
	 * @throws BusinessException 
	 */ 
	public static String getAccessTokenString(String appid, String appsecret) throws BusinessException {  
		AccessToken accessToken = getAccessToken(appid, appsecret);
		return accessToken.getToken();
	}
	/** 
	 * 获取access_token 
	 * @param appid 凭证 
	 * @param appsecret 密钥 
	 * @return 
	 * @throws BusinessException 
	 */  
	public static AccessToken getAccessToken(String appid, String appsecret) throws BusinessException {  
	    String requestUrl = Constants.GET_ACCESSTOKEN_URL.replace("APPID", appid).replace("APPSECRET", appsecret);  
	    JSONObject jsonObject = httpRequest("获取token失败", requestUrl, "GET");
	    AccessToken accessToken = new AccessToken();  
        accessToken.setToken(jsonObject.getString("access_token"));  
        accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
	    return accessToken;  
	} 
	
	/**
	 * 获得JSAPI_TICKET
	 * @param appid 凭证
	 * @param appsecret 密钥
	 * @return
	 * @throws BusinessException
	 */
	public static String getJsapiTicket(String appid, String appsecret) throws BusinessException {
		//先查缓存
		Object JSAPI_TICKET = EhcacheUtil.getInstance().get(Constants.JSAPI_TICKET);//缓存的ACCESS_TOKEN
		Object JSAPI_TICKET_TIME = EhcacheUtil.getInstance().get(Constants.JSAPI_TICKET_TIME);//缓存ACCESS_TOKEN的时间
		if(JSAPI_TICKET != null && JSAPI_TICKET_TIME != null){
			//通过比对时间，判断JSAPI_TICKET是否过期
			Date old_date = (Date) JSAPI_TICKET_TIME;//缓存时间
			Date now_date = new Date();//当前时间
			long between = now_date.getTime() - old_date.getTime();//时间差
			if(between < (2* 3600000)){
				return JSAPI_TICKET.toString();
			}
		}
		
		String url = Constants.GET_JSAPI_TICKET_URL.replace("ACCESS_TOKEN", getAccessTokenString(appid, appsecret));
		JSONObject jsonObject = httpRequest("获取jsapiTicket失败", url, "GET");
		if(jsonObject != null) {
			if(jsonObject.get("errcode") != null && jsonObject.getInt("errcode") != 0) {
				throw new BusinessException(jsonObject.getString("errmsg"));
			}
			if(jsonObject.getString("ticket") != null && !"".equals(jsonObject.getString("ticket"))) {
				String ticket = jsonObject.getString("ticket");
				return ticket;
			}
		}
		return null;
	}
	
	/**
	 * 网页授权通过code换取access_token等数据
	 * @param appid 凭证
	 * @param appsecret 密钥
	 * @param code code
	 * @return
	 * @throws Exception
	 */
	public static Map<String, Object> getOauthAccessToken(String appid, String appsecret, String code) throws Exception{
		String url = Constants.GET_OAUTH_ACCESSTOKEN_URL.replace("APPID", appid).replace("SECRET", appsecret).replace("CODE", code);
		JSONObject jsonObject = httpRequest("获取OauthAccessToken失败", url, "GET");
		if(jsonObject != null) {
			if(jsonObject.get("errcode") != null) {
				throw new BusinessException(jsonObject.getString("errmsg"));
			}
			Map<String ,Object> map = new HashMap<String ,Object>();
			map.put("access_token", jsonObject.getString("access_token"));
			map.put("expires_in", jsonObject.getInt("expires_in"));
			map.put("refresh_token", jsonObject.getString("refresh_token"));
			map.put("openid", jsonObject.getString("openid"));
			map.put("scope", jsonObject.getString("scope"));
			return map;
		}
		return null;
	}
	
	/**
	 * 获得临时的带参数的二维码
	 * @param appid
	 * @param appsecret
	 * @param expire_seconds 该二维码有效时间，以秒为单位。 最大不超过2592000（即30天）
	 * @param scene_str 场景值ID（字符串形式的ID），字符串类型，长度限制为1到64
	 * @return
	 * @throws BusinessException
	 */
	public static String getTemporaryQRcode(String appid, String appsecret, int expire_seconds, String scene_str) throws BusinessException {
		String url = Constants.GET_TICKET_URL.replace("ACCESS_TOKEN", getAccessTokenString(appid, appsecret));
		String jsonMsg = "{\"expire_seconds\": "+ expire_seconds +", \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\":\""+ scene_str +"\"}}}";
		JSONObject jsonObject = httpRequest("获取二维码失败 ", url, "POST", jsonMsg);
		if(jsonObject != null) {
			if(jsonObject.get("errcode") != null) {
				throw new BusinessException(jsonObject.getString("errmsg"));
			}
			if(jsonObject.getString("ticket") != null && !"".equals(jsonObject.getString("ticket"))) {
				String ticket = jsonObject.getString("ticket");
				return Constants.SHOW_QRCODE_URL.replace("TICKET", ticket);
			}
		}
		return null;
	}
	
	/**
	 * 获得永久的带参数的二维码
	 * @param appid
	 * @param appsecret
	 * @param scene_str 场景值ID（字符串形式的ID），字符串类型，长度限制为1到64
	 * @return
	 * @throws BusinessException
	 */
	public static String getperpetualQRcode(String appid, String appsecret, String scene_str) throws BusinessException {
		String url = Constants.GET_TICKET_URL.replace("ACCESS_TOKEN", getAccessTokenString(appid, appsecret));
		String jsonMsg = "{\"action_name\": \"QR_LIMIT_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": "+ scene_str +"}}}";
		JSONObject jsonObject = httpRequest("获取二维码失败 ", url, "POST", jsonMsg);
		if(jsonObject != null) {
			if(jsonObject.getInt("errcode") != 0) {
				throw new BusinessException(jsonObject.getString("errmsg"));
			}
			if(jsonObject.getString("ticket") != null && !"".equals(jsonObject.getString("ticket"))) {
				return jsonObject.getString("ticket");
			}
		}
		return null;
	}
	
	/**
	 * 获得用户详细信息
	 * @param appid
	 * @param appsecret
	 * @param openid
	 * @return
	 * @throws BusinessException
	 */
	public static Map<String, Object> getUserInfo(String appid, String appsecret, String openid) throws BusinessException {
		String url = Constants.GET_USERINFO_URL.replace("ACCESS_TOKEN", getAccessTokenString(appid, appsecret)).replace("OPENID", openid);
		JSONObject jsonObject = httpRequest("获取用户详细信息失败 ", url, "POST");
		if(jsonObject != null) {
			if(jsonObject.get("errcode") != null) {
				throw new BusinessException(jsonObject.getString("errmsg"));
			}
			Map<String, Object> map = new HashMap<String, Object>();
			map.put("subscribe", jsonObject.getString("subscribe"));//用户是否订阅该公众号标识，值为0时，代表此用户没有关注该公众号，拉取不到其余信息。
			map.put("openid", jsonObject.getString("openid"));//用户的标识，对当前公众号唯一
			map.put("nickname", jsonObject.getString("nickname"));//用户的昵称
			map.put("sex", jsonObject.getString("sex"));//用户的性别，值为1时是男性，值为2时是女性，值为0时是未知
			map.put("language", jsonObject.getString("language"));//用户的语言，简体中文为zh_CN
			map.put("city", jsonObject.getString("city"));//用户所在城市
			map.put("province", jsonObject.getString("province"));//用户所在省份
			map.put("country", jsonObject.getString("country"));//用户所在国家
			map.put("headimgurl", jsonObject.getString("headimgurl"));//用户头像，最后一个数值代表正方形头像大小（有0、46、64、96、132数值可选，0代表640*640正方形头像），用户没有头像时该项为空。若用户更换头像，原有头像URL将失效。
			map.put("subscribe_time", jsonObject.getString("subscribe_time"));//用户关注时间，为时间戳。如果用户曾多次关注，则取最后关注时间
			return map;
		}
		return null;
	}
	
	/**
	 * 发送模板消息
	 * @param appid
	 * @param appsecret
	 * @param templateid 模板ID
	 * @param openid 接受用户openid
	 * @param jsonMsg 参数json
	 * @throws BusinessException
	 */
	public static void sendTemplateMessage(String appid, String appsecret, String jsonMsg) throws BusinessException {
		String url = Constants.MESSAGE_TEMPLATE_URL.replace("ACCESS_TOKEN", getAccessTokenString(appid, appsecret));
		JSONObject jsonObject = httpRequest("发送模板消息失败 ", url, "POST", jsonMsg);
		if(jsonObject != null) {
			if(jsonObject.getInt("errcode") != 0) {
				throw new BusinessException(jsonObject.getString("errmsg"));
			}
		}
	}

	/**
	 * 发起https请求并获取结果
	 * @param requestUrl 请求地址
	 * @param requestMethod 请求方式（GET、POST）
	 * @param outputStr 提交的数据
	 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
	 * @throws BusinessException 
	 */
	public static JSONObject httpRequest(String message, String requestUrl, String requestMethod) throws BusinessException {
		return httpRequest(message, requestUrl, requestMethod, null);
	}			
	public static JSONObject httpRequest(String message, String requestUrl, String requestMethod, String outputStr) throws BusinessException {
		JSONObject jsonObject = null;
		StringBuffer buffer = new StringBuffer();
		try {
			// 创建SSLContext对象，并使用我们指定的信任管理器初始化
			TrustManager[] tm = { new MyX509TrustManager() };
			SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
			sslContext.init(null, tm, new java.security.SecureRandom());
			// 从上述SSLContext对象中得到SSLSocketFactory对象
			SSLSocketFactory ssf = sslContext.getSocketFactory();

			URL url = new URL(requestUrl);
			HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
			httpUrlConn.setSSLSocketFactory(ssf);

			httpUrlConn.setDoOutput(true);
			httpUrlConn.setDoInput(true);
			httpUrlConn.setUseCaches(false);
			// 设置请求方式（GET/POST）
			httpUrlConn.setRequestMethod(requestMethod);

			if ("GET".equalsIgnoreCase(requestMethod))
				httpUrlConn.connect();

			// 当有数据需要提交时
			if (null != outputStr) {
				OutputStream outputStream = httpUrlConn.getOutputStream();
				// 注意编码格式，防止中文乱码
				outputStream.write(outputStr.getBytes("UTF-8"));
				outputStream.close();
			}

			// 将返回的输入流转换成字符串
			InputStream inputStream = httpUrlConn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

			String str = null;
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			bufferedReader.close();
			inputStreamReader.close();
			// 释放资源
			inputStream.close();
			inputStream = null;
			httpUrlConn.disconnect();
			jsonObject = JSONObject.fromObject(buffer.toString());
		    MessageUtil.checkMessage(message, jsonObject); 
		} catch (BusinessException be) {
			throw be;
		} catch (ConnectException ce) {
			log.error("Weixin server connection timed out.");
			throw new BusinessException("连接时异常，微信服务器连接超时!");
		} catch (Exception e) {
			log.error("https request error:{}", e);
			throw new BusinessException("访问未知异常，" + e.toString() + e.getMessage());
		}
		return jsonObject;
	}
	
	/** 
	 * 创建菜单 
	 *  
	 * @param menu 菜单实例 
	 * @param appID 有效的应用ID 
	 * @param appSecret 有效的密钥
	 * @return 0表示成功，其他值表示失败 
	 * @throws BusinessException 
	 */
	public static void createMenu(Menu menu, String appID, String appSecret) throws BusinessException {		  
		// 拼装创建菜单的url
	    String url = Constants.MENU_CREATE_URL.replace("ACCESS_TOKEN", getAccessTokenString(appID, appSecret));
	    // 将菜单对象转换成json字符串
	    String jsonMenu = JSONObject.fromObject(menu).toString();
	    // 调用接口创建菜单
	    httpRequest("菜单创建失败 ", url, "POST", jsonMenu);
	}
	
	/** 
	 * 查询菜单 
	 * @param appID 有效的应用ID 
	 * @param appSecret 有效的密钥
	 * @return menu 菜单实例 
	 * @throws BusinessException 
	 */
	public static Menu queryMenu(String appID, String appSecret) throws BusinessException {
	    // 拼装创建菜单的url
	    String url = Constants.MENU_QUERY_URL.replace("ACCESS_TOKEN", getAccessTokenString(appID, appSecret));
	    // 调用接口创建菜单
	    JSONObject jsonObject = httpRequest("菜单查询失败", url, "GET");
	    jsonObject = jsonObject.getJSONObject("menu");
	    JSONArray jsonArray = jsonObject.getJSONArray("button");
	    Menu menu = new Menu();
	    menu.setButton(getButtonByJsonObject(jsonArray));
	    return menu;
	}
	private static Button[] getButtonByJsonObject(JSONArray jsonArray){
		if (jsonArray.isEmpty())
	    	return null;
		List<Button> buttonList = new ArrayList<Button>();
		JSONArray array = null;
		JSONObject jsonObject = null;
	    Button button = null;
	    for (int i = 0; i < jsonArray.size(); i++) {
	    	jsonObject = jsonArray.getJSONObject(i);
	    	button = new Button();
	    	if (jsonObject.containsKey("name"))
	    		button.setName(jsonObject.getString("name"));
	    	if (jsonObject.containsKey("type"))
	    		button.setType(jsonObject.getString("type"));
	    	if (jsonObject.containsKey("key"))
	    		button.setKey(jsonObject.getString("key"));
	    	if (jsonObject.containsKey("url"))
	    		button.setUrl(jsonObject.getString("url"));
	    	array = jsonObject.getJSONArray("sub_button");
	    	button.setSub_button(getButtonByJsonObject(array));
	    	buttonList.add(button);
		}
		return (Button[])buttonList.toArray(new Button[0]);
	}
}